﻿using System;
using System.Diagnostics;

namespace BReusable
{

	/// <summary>
	///
	/// </summary>
	/// <typeparam name="TContext"></typeparam>
	/// <typeparam name="TEntity"></typeparam>
	/// <remarks> State: _readonlyEntity and _liveEntity are the same means new entity that is not attached
	/// _readonlyEntity !=null and _liveEntity=null; object that has not been edited.</remarks>
	public abstract class BufferedLinqEntity2<TContext, TEntity>
		where TContext : System.Data.Linq.DataContext
		where TEntity : class
	{
		/// <summary>
		/// Should never be null
		/// </summary>
		TEntity _readonlyEntity;

		LiveObject _liveEntity;
		readonly Func<TContext> _createNewContext;
		readonly Func<TContext, TEntity> _getDbEntity;
		/*
				private bool _IsNew;
		*/



		private class LiveObject
		{
			public TContext DataContext { get; private set; }
			public TEntity Entity { get; private set; }

			public LiveObject(Func<TContext> createNewContext, Func<TContext, TEntity> getDbEntity)
			{
				DataContext = createNewContext();
				Entity = getDbEntity(DataContext);
			}
			public LiveObject(TContext dataContext, TEntity newEntity)
			{
				DataContext = dataContext;
				Entity = newEntity;
			}

		}

		public TEntity GetEntity()
		{
			if (_liveEntity != null)
				return _liveEntity.Entity;
			return _readonlyEntity;
		}

		#region Constructors


		///// <summary>
		///// Gets the entity, stores it using the function
		///// for entities obtained from other relationships
		///// </summary>
		///// <param name="createNewContext">if a custom connection string is needed put it here</param>
		///// <param name="getDbEntity">should use primary key to get back the same record again</param>
		//public BufferedLinqEntity2(Func<TContext> createNewContext, Func<TContext, TEntity> getDbEntity)
		//{
		//    _createNewContext = createNewContext;
		//    _getDbEntity = getDbEntity;
		//    //readonlyContext.ObjectTrackingEnabled = false;
		//    _readonlyEntity = getDbEntity(createNewContext());

		//}


		/// <summary>
		/// Accepts an entity with whatever settings are preferred, for entities that were attached elsewhere
		/// </summary>
		/// <param name="createNewContext"></param>
		/// <param name="getDbEntity"></param>
		/// <param name="currentEntity"></param>
		protected BufferedLinqEntity2(Func<TContext> createNewContext,
			Func<TContext, TEntity> getDbEntity, TEntity currentEntity)
		{
			_createNewContext = createNewContext;
			_getDbEntity = getDbEntity;
			_readonlyEntity = currentEntity;
		}

		/// <summary>
		/// Used for new entities that are not attached to anything
		/// </summary>
		/// <param name="createNewContext"></param>
		/// <param name="getDbEntityFromCurrent"></param>
		/// <param name="newEntityCode"></param>
		/// <param name="insertAction"></param>
		protected BufferedLinqEntity2(Func<TContext> createNewContext, Func<TContext, TEntity, TEntity> getDbEntityFromCurrent
				, TEntity newEntityCode, Action<TContext, TEntity> insertAction)
		{
			_createNewContext = createNewContext;
			_getDbEntity = dc => getDbEntityFromCurrent(dc, _readonlyEntity);
			_readonlyEntity = newEntityCode;
			var newContext = createNewContext();
			insertAction(newContext, newEntityCode);
			_liveEntity = new LiveObject(newContext, newEntityCode);
		}

		#endregion

		#region Get/Set

		protected TFieldType GetBufferedProperty<TFieldType>(Func<TEntity, TFieldType> getValueFunc)
		{
			if (_liveEntity != null)
				return getValueFunc(_liveEntity.Entity);
			return getValueFunc(_readonlyEntity);
		}


		protected void SetBufferedProperty(Action<TEntity> linqAction)
		{
			BeginEdit();
			//If something changes and context does exist, make change directly
			linqAction(_liveEntity.Entity);
		}


		#endregion


		#region EditingMethods

		//if save is called try to commit changes
		//	if error report back or throw?
		//	if no error move _live entity to readonlyEntity

		public void BeginEdit()
		{
			//Does this need to be implemented at all?
			// when is this called when used with a bindingsource/binding navigator
			if (_liveEntity == null)
			{
				//If something changes, and context does not exist, create context
				_liveEntity = new LiveObject(_createNewContext, _getDbEntity);
			}
		}

		public void CancelEdit()
		{
			if(IsNewAndUnattached==false)
			_liveEntity = null;
		}

		public void EndEdit()
		{
			var newAndUnattached = IsNewAndUnattached;

			if (_liveEntity != null)
			{
				OnEntitySaving(_readonlyEntity, _liveEntity.Entity);
				_liveEntity.DataContext.SubmitChanges();
				var oldEntity = _readonlyEntity;
				_readonlyEntity = _liveEntity.Entity;
				_liveEntity = null;
				try
				{
					OnEntitySaved(oldEntity);
				}
				finally
				{
					if (newAndUnattached) OnNewEntitySaved();
				}

			}
		}

		public bool IsNewAndUnattached
		{
			get
			{
				return _liveEntity != null
					&& ReferenceEquals(_liveEntity.Entity, _readonlyEntity);
			}
		}

		/// <summary>
		/// Performs the actual deleteOnSubmit and saveChanges
		/// </summary>
		/// <param name="dc"></param>
		/// <param name="entityToDelete"></param>
		protected abstract void DeleteEntity(TContext dc, TEntity entityToDelete);

		public void DeleteEntity()
		{
			if (IsNewAndUnattached)
			//entity is new and unattached
			{
				_liveEntity = null;
				_readonlyEntity = null;
			}
			else
			{
				if (_liveEntity != null)
				{
					DeleteEntity(_liveEntity.DataContext, _liveEntity.Entity);
				}
				else if (_readonlyEntity != null)
				{
					_liveEntity = new LiveObject(_createNewContext, _getDbEntity);
					DeleteEntity(_liveEntity.DataContext, _liveEntity.Entity);
				}

			}

		}
		#endregion

		virtual protected void OnEntitySaving(TEntity oldEntity, TEntity newEntity) { }
		virtual protected void OnEntitySaved(TEntity oldEntity) { }
		virtual protected void OnNewEntitySaved() { }

		public bool HasChanges
		{
			get { return _liveEntity == null ? false : _liveEntity.DataContext.HasChanges(); }
		}


	}
}
